<?php
/**
 * Array helper class.
 *
 * $Id: arr.php 3769 2008-12-15 00:48:56Z zombor $
 *
 * @package    Core
 * @author     Kohana Team
 * @copyright  (c) 2007-2008 Kohana Team
 * @license    http://kohanaphp.com/license.html
 */
class CArray
{

    /**
     * Return a callback array from a string, eg: limit[10,20] would become
     * array('limit', array('10', '20'))
     *
     * @param   string  callback string
     * @return  array
     */
    public static function callback_string($str)
    {
        // command[param,param]
        if (preg_match('/([^\[]*+)\[(.+)\]/', (string)$str, $match)) {
            // command
            $command = $match[1];

            // param,param
            $params = preg_split('/(?<!\\\\),/', $match[2]);
            $params = str_replace('\,', ',', $params);
        }
        else {
            // command
            $command = $str;

            // No params
            $params = NULL;
        }

        return array($command, $params);
    }

    /**
     * Rotates a 2D array clockwise.
     * Example, turns a 2x3 array into a 3x2 array.
     *
     * @param   array    array to rotate
     * @param   boolean  keep the keys in the final rotated array. the sub arrays of the source array need to have the same key values.
     *                   if your subkeys might not match, you need to pass FALSE here!
     * @return  array
     */
    public static function rotate($source_array, $keep_keys = TRUE)
    {
        $new_array = array();
        foreach ($source_array as $key => $value) {
            $value = ($keep_keys === TRUE) ? $value : array_values($value);
            foreach ($value as $k => $v) {
                $new_array[$k][$key] = $v;
            }
        }

        return $new_array;
    }

    /**
     * Removes a key from an array and returns the value.
     *
     * @param   string  key to return
     * @param   array   array to work on
     * @return  mixed   value of the requested array key
     */
    public static function remove($key, & $array)
    {
        if (!array_key_exists($key, $array))
            return NULL;

        $val = $array[$key];
        unset($array[$key]);

        return $val;
    }


    /**
     * Extract one or more keys from an array. Each key given after the first
     * argument (the array) will be extracted. Keys that do not exist in the
     * search array will be NULL in the extracted data.
     *
     * @param   array   array to search
     * @param   string  key name
     * @return  array
     */
    public static function extract(array $search, $keys)
    {
        // Get the keys, removing the $search array
        $keys = array_slice(func_get_args(), 1);

        $found = array();
        foreach ($keys as $key) {
            if (isset($search[$key])) {
                $found[$key] = $search[$key];
            }
            else {
                $found[$key] = NULL;
            }
        }

        return $found;
    }

    /**
     * Because PHP does not have this function.
     *
     * @param   array   array to unshift
     * @param   string  key to unshift
     * @param   mixed   value to unshift
     * @return  array
     */
    public static function unshift_assoc(array & $array, $key, $val)
    {
        $array = array_reverse($array, TRUE);
        $array[$key] = $val;
        $array = array_reverse($array, TRUE);

        return $array;
    }

    /**
     * Because PHP does not have this function, and array_walk_recursive creates
     * references in arrays and is not truly recursive.
     *
     * @param   mixed  callback to apply to each member of the array
     * @param   array  array to map to
     * @return  array
     */
    public static function map_recursive($callback, array $array)
    {
        foreach ($array as $key => $val) {
            // Map the callback to the key
            $array[$key] = is_array($val) ? arr::map_recursive($callback, $val) : call_user_func($callback, $val);
        }

        return $array;
    }

    /**
     * Binary search algorithm.
     *
     * @param   mixed    the value to search for
     * @param   array    an array of values to search in
     * @param   boolean  return false, or the nearest value
     * @param   mixed    sort the array before searching it
     * @return  integer
     */
    public static function binary_search($needle, $haystack, $nearest = FALSE, $sort = FALSE)
    {
        if ($sort === TRUE) {
            sort($haystack);
        }

        $high = count($haystack);
        $low = 0;

        while ($high - $low > 1) {
            $probe = ($high + $low) / 2;
            if ($haystack[$probe] < $needle) {
                $low = $probe;
            }
            else {
                $high = $probe;
            }
        }

        if ($high == count($haystack) OR $haystack[$high] != $needle) {
            if ($nearest === FALSE)
                return FALSE;

            // return the nearest value
            $high_distance = $haystack[ceil($low)] - $needle;
            $low_distance = $needle - $haystack[floor($low)];

            return ($high_distance >= $low_distance) ? $haystack[ceil($low)] : $haystack[floor($low)];
        }

        return $high;
    }

    /**
     * Emulates array_merge_recursive, but appends numeric keys and replaces
     * associative keys, instead of appending all keys.
     *
     * @param   array  any number of arrays
     * @return  array
     */
    public static function merge()
    {
        $total = func_num_args();

        $result = array();
        for ($i = 0; $i < $total; $i++) {
            foreach (func_get_arg($i) as $key => $val) {
                if (isset($result[$key])) {
                    if (is_array($val)) {
                        // Arrays are merged recursively
                        $result[$key] = arr::merge($result[$key], $val);
                    }
                    elseif (is_int($key)) {
                        // Indexed arrays are appended
                        array_push($result, $val);
                    }
                    else {
                        // Associative arrays are replaced
                        $result[$key] = $val;
                    }
                }
                else {
                    // New values are added
                    $result[$key] = $val;
                }
            }
        }

        return $result;
    }

    /**
     * Overwrites an array with values from input array(s).
     * Non-existing keys will not be appended!
     *
     * @param   array   key array
     * @param   array   input array(s) that will overwrite key array values
     * @return  array
     */
    public static function overwrite($array1)
    {
        foreach (array_slice(func_get_args(), 1) as $array2) {
            foreach ($array2 as $key => $value) {
                if (array_key_exists($key, $array1)) {
                    $array1[$key] = $value;
                }
            }
        }

        return $array1;
    }

    /**
     * Fill an array with a range of numbers.
     *
     * @param   integer  stepping
     * @param   integer  ending number
     * @return  array
     */
    public static function range($step = 10, $max = 100)
    {
        if ($step < 1)
            return array();

        $array = array();
        for ($i = $step; $i <= $max; $i += $step) {
            $array[$i] = $i;
        }

        return $array;
    }

    /**
     * Recursively convert an array to an object.
     *
     * @param   array   array to convert
     * @return  object
     */
    public static function to_object(array $array, $class = 'stdClass')
    {
        $object = new $class;

        foreach ($array as $key => $value) {
            if (is_array($value)) {
                // Convert the array to an object
                $value = arr::to_object($value, $class);
            }

            // Add the value to the object
            $object->{$key} = $value;
        }

        return $object;
    }

} // End arr